#include "cirbuf.h"

#define MIN(a,b) (((a)<(b))?(a):(b))

typedef enum {
    BUF_UNUSED = 0,
    BUF_USED = 1
} CirBufStatus;

int32_t CirBufInit(CirBuf *cirbCB, char *fifo, uint32_t size)
{
    if ((cirbCB == NULL) || (fifo == NULL)) {
        return -1;
    }

    memset(cirbCB, 0, sizeof(CirBuf));
    cirbCB->size = size;
    cirbCB->remain = size;
    cirbCB->status = BUF_USED;
    cirbCB->fifo = fifo;

    return 0;
}

void CirBufDeinit(CirBuf *cirbCB)
{
    memset(cirbCB, 0, sizeof(CirBuf));
}

int32_t CirBufUsedSize(CirBuf *cirbCB)
{
    uint32_t size;
    size = cirbCB->size - cirbCB->remain;
    return size;
}

static uint32_t CirBufWriteLinear(CirBuf *cirbCB, const char *buf, uint32_t size)
{
    uint32_t cpSize;

    // get copy size
    cpSize = MIN(cirbCB->remain, size);

    if (cpSize == 0) {
        return 0;
    }

    // copy to buffer
    memcpy((void *)(cirbCB->fifo + cirbCB->wIdx), buf, cpSize);

    cirbCB->remain -= cpSize;
    cirbCB->wIdx += cpSize;

    // write point roll to start
    if (cirbCB->wIdx >= cirbCB->size) {
        cirbCB->wIdx = 0;
    }

    return cpSize;
}

static uint32_t CirBufWriteLoop(CirBuf *cirbCB, const char *buf, uint32_t size)
{
    uint32_t right, cpSize;

    // get upper part space
    right = cirbCB->size - cirbCB->wIdx;

    // get upper part copy size
    cpSize = MIN(right, size);

    // copy upper part
    cpSize = CirBufWriteLinear(cirbCB, buf, cpSize);
    if (cpSize == 0) {
        printf("%s: something is wrong in CirBufWriteLinear.\r\n", __func__);
        return 0;
    }

    // copy lower part (if needed)
    if (cpSize != size) {
        cpSize += CirBufWriteLinear(cirbCB, buf + cpSize, size - cpSize);
    }

    return cpSize;
}

int32_t CirBufWrite(CirBuf *cirbCB, const char *buf, uint32_t size)
{
    uint32_t cpSize;

    if ((cirbCB == NULL) || (buf == NULL) || (size == 0)) {
        return 0;
    }

    if ((cirbCB->fifo == NULL) || (cirbCB->remain == 0))  {
        return 0;
    }

    if (cirbCB->rIdx <= cirbCB->wIdx) {
        cpSize = CirBufWriteLinear(cirbCB, buf, size);
    } else {
        cpSize = CirBufWriteLoop(cirbCB, buf, size);
    }

    return cpSize;
}

static uint32_t CirBufReadLinear(CirBuf *cirbCB, const char *buf, uint32_t size)
{
    uint32_t cpSize;

    // this time max size
    cpSize = MIN((cirbCB->size - cirbCB->remain),(cirbCB->size - cirbCB->rIdx));

    // copy size
    cpSize = MIN(cpSize, size);

    if (cpSize == 0) {
        return 0;
    }

    // copy data
    memcpy((void *)buf, (void *)(cirbCB->fifo + cirbCB->rIdx), cpSize);

    cirbCB->remain += cpSize;
    cirbCB->rIdx += cpSize;

    if (cirbCB->rIdx >= cirbCB->size) {
        cirbCB->rIdx = 0;
    }

    return cpSize;
}

static uint32_t CirBufReadLoop(CirBuf *cirbCB, const char *buf, uint32_t size)
{
    uint32_t right, cpSize;

    // get upper part size
    right = cirbCB->size - cirbCB->rIdx;

    // get upper part copy size
    cpSize = MIN(right, size);

    // copy upper part
    cpSize = CirBufReadLinear(cirbCB, buf, cpSize);
    if (cpSize == 0) {
        printf("%s: something is wrong in CirBufReadLinear.\r\n", __func__);
        return 0;
    }

    // copy lower part (if needed)
    if (cpSize < size) {
        cpSize += CirBufReadLinear(cirbCB, buf + cpSize, size - cpSize);
    }

    return cpSize;
}

int32_t CirBufRead(CirBuf *cirbCB, char *buf, uint32_t size)
{
    uint32_t cpSize;

    if ((cirbCB == NULL) || (buf == NULL) || (size == 0)) {
        return 0;
    }

    if ((cirbCB->fifo == NULL) || (cirbCB->remain == cirbCB->size)) {
        return 0;
    }

    if (cirbCB->rIdx >= cirbCB->wIdx) {
        cpSize = CirBufReadLoop(cirbCB, buf, size);
    } else {
        cpSize = CirBufReadLinear(cirbCB, buf, size);
    }

    return cpSize;
}
